/*
 *  Copyright 2004 Blandware (http://www.blandware.com)
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package com.blandware.atleap.webapp.taglib.core;

import com.blandware.atleap.common.util.DateUtil;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.commons.validator.GenericValidator;
import org.apache.struts.Globals;
import org.apache.struts.taglib.TagUtils;

import javax.servlet.jsp.JspException;
import javax.servlet.jsp.JspTagException;
import javax.servlet.jsp.PageContext;
import javax.servlet.jsp.tagext.SimpleTagSupport;
import java.text.DateFormat;
import java.text.ParseException;
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;

/**
 * <p>Formats date according to specified pattern. If variable is specified,
 * formatted date is saved to it, else it's written to the page.
 * If no pattern is specified, it is taken according to locale, stored in session under
 * <code>org.apache.struts.Globals.LOCALE_KEY</code>
 * </p>
 * <p>This tag supports instances of following classes as input value:
 * <ul type="disc">
 * <li>java.lang.String (see below for information about reserved words)</li>
 * <li>java.util.Date</li>
 * <li>java.sql.Date</li>
 * <li>java.sql.Timestamp</li>
 * <li>java.util.Calendar</li>
 * </ul>
 * </p>
 * You can specify following reserved words as string value:
 * <ul type="disc">
 * <li><em>now</em> - value will be treated as today (you can also omit <code>value</code> attribute to gain the same effect)</li>
 * <li><em>epoch</em> - value will be the date when Unix epoch has started: January 1, 1970</li>
 * </ul>
 * If <b>value</b> is a String and is not one of these reserved words, it's
 * tried to be parsed according to MM/dd/yyyy pattern in "en" locale.
 * <p>
 * Full list of allowed attributes:
 * <ul>
 * <li>
 * <b>value</b> - represents date that needs to be formatted (see above)
 * </li>
 * <li>
 * <b>locale</b> - represents locale to format date to. Can be
 * <code>String</code> (for example, "en_US") or <code>java.util.Locale</code>
 * </li>
 * <li>
 * <b>type</b> - type of formatter: date, time or datetime. Default is date.
 * This is ignored if <b>pattern</b> is specified.
 * </li>
 * <li>
 * <b>dateStyle</b> - date style. Can be either SHORT, MEDIUM, LONG or FULL.
 * See <code>java.text.DateFormat</code> for more information. This is ignored
 * if <b>pattern</b> is specified.
 * </li>
 * <li>
 * <b>timeStyle</b> - time style. Can be either SHORT, MEDIUM, LONG or FULL.
 * See <code>java.text.DateFormat</code> for more infomation. This is ignored
 * if <b>pattern</b> is specified.
 * </li>
 * <li>
 * <b>pattern</b> - pattern which will be used to format date. If this is
 * specified, <b>type</b>, <b>dateStyle</b> and <b>timeStyle</b> are ignored.
 * Format is same as in {@link java.text.SimpleDateFormat}.
 * </li>
 * <li>
 * <b>var</b> - name of variable to export formatted date. If not given, result
 * is not exported but written to the page
 * </li>
 * <li>
 * <b>scope</b> - scope of variable to export formatted date. If <code>var</code>
 * is not specified, this attribute is ignored
 * </li>
 * </ul>
 * </p>
 * <p>
 * Example of use is <code>&lt;atleap:formatDate value="now" type="datetime" /&gt;</code>.
 * This will render current date and time for current locale (which is stored in
 * session). 
 * </p>
 * <p><a href="FormatDateTag.java.html"><i>View Source</i></a></p>
 *
 * @author Sergey Zubtsovskiy <a href="mailto:sergey.zubtsovskiy@blandware.com">&lt;sergey.zubtsovskiy@blandware.com&gt;</a>
 * @version $Revision: 1.20 $ $Date: 2008/01/14 10:59:49 $
 * @jsp.tag name="formatDate"
 * body-content="empty"
 * @see org.apache.struts.Globals#LOCALE_KEY
 * @see java.text.SimpleDateFormat
 */
public class FormatDateTag extends SimpleTagSupport {

	protected transient final Log log = LogFactory.getLog(FormatDateTag.class);

	/**
	 * Object contains date to be formatted.
	 * Supports instances of <code>java.util.Date</code>, <code>java.sql.Date</code>,
	 * <code>java.sql.Timestamp</code> and <code>java.util.Calendar</code>.
	 */
	protected Object value;

	/**
	 * Locale to format date according to. Supports instances of <code>java.lang.String</code> or <code>java.util.Locale</code>
	 */
	protected Object locale;

	/**
	 * Type of formatter: date, time or datetime. Default is date.
	 */
	protected String type = "date";

	/**
	 * Date style. Can be either SHORT, MEDIUM, LONG or FULL.
	 * See <code>java.text.DateFormat</code> for more infomation
	 *
	 * @see java.text.DateFormat
	 */
	protected String dateStyle = "medium";

	/**
	 * Time style. Can be either SHORT, MEDIUM, LONG or FULL.
	 * See <code>java.text.DateFormat</code> for more infomation
	 *
	 * @see java.text.DateFormat
	 */
	protected String timeStyle = "medium";

    /**
     * Pattern to format date/time. See <code>java.text.SimpleDateFormat</code>
     */
    protected String pattern = null;

	/**
	 * Name of variable to export formatted date
	 */
	protected String var;

	/**
	 * Scope to export variable to
	 */
	protected String scope;

	/**
     * Returns date to be formatted
     *
	 * @return date to be formatted
     * @see #value
	 * @jsp.attribute required="false"
	 * rtexprvalue="true"
	 * type="java.lang.Object"
	 * description="Object contains date to be formatted."
	 */
	public Object getValue() {
		return value;
	}

    /**
     * Sets date to be formatted
     *
     * @param value date to be formatted
     * @see #value
     */
	public void setValue(Object value) {
		this.value = value;
	}

	/**
     * Returns locale
     *
	 * @return locale
     * @see #locale
	 * @jsp.attribute required="false"
	 * rtexprvalue="true"
	 * type="java.lang.Object"
	 * description="Locale to format date according to"
	 */
	public Object getLocale() {
		return locale;
	}

    /**
     * Sets locale
     *
     * @param locale locale to set
     * @see #locale
     */
	public void setLocale(Object locale) {
		this.locale = locale;
	}

	/**
     * Returns type of formatter
     *
	 * @return type of formatter
     * @see #type
	 * @jsp.attribute required="false"
	 * rtexprvalue="true"
	 * type="java.lang.String"
	 * description="Type of formatter: date, time or datetime. Default is date"
	 */
	public String getType() {
		return type;
	}

    /**
     * Sets type of formatter
     *
     * @param type type of formatter to set
     * @see #type
     */
	public void setType(String type) {
		this.type = type;
	}

	/**
     * Returns style of date
     *
	 * @return style of date
     * @see #dateStyle
	 * @jsp.attribute required="false"
	 * rtexprvalue="true"
	 * type="java.lang.String"
	 * description="Date style"
	 */
	public String getDateStyle() {
		return dateStyle;
	}

    /**
     * Sets style of date
     *
     * @param dateStyle style of date to set
     * @see #dateStyle
     */
	public void setDateStyle(String dateStyle) {
		this.dateStyle = dateStyle;
	}

	/**
     * Returns style of time
     *
	 * @return style of time
     * @see #timeStyle
	 * @jsp.attribute required="false"
	 * rtexprvalue="true"
	 * type="java.lang.String"
	 * description="Time style"
	 */
	public String getTimeStyle() {
		return timeStyle;
	}

    /**
     * Sets style of time
     *
     * @param timeStyle style of time to set
     * @see #timeStyle
     */
	public void setTimeStyle(String timeStyle) {
		this.timeStyle = timeStyle;
	}

    /**
     * Returns formatting pattern.
     *
     * @return pattern
     * @see #pattern
     * @jsp.attribute required="false"
	 * rtexprvalue="true"
	 * type="java.lang.String"
	 * description="Formatting pattern"
     */
    public String getPattern() {
        return pattern;
    }

    /**
     * Sets formatting pattern.
     *
     * @param pattern pattern to set
     */
    public void setPattern(String pattern) {
        this.pattern = pattern;
    }

	/**
     * Returns name of variable that will accept formatted date
     *
	 * @return name of variable
     * @see #var
	 * @jsp.attribute required="false"
	 * rtexprvalue="true"
	 * type="java.lang.String"
	 * description="Name of variable to export formatted date"
	 */
	public String getVar() {
		return var;
	}

    /**
     * Sets name of variable that will accept formatted date
     *
     * @param var name of variable to set
     * @see #var
     */
	public void setVar(String var) {
		this.var = var;
	}

	/**
     * Returns scope of variable
     *
     * @return scope of variable
     * @see #scope
	 * @jsp.attribute required="false"
	 * rtexprvalue="true"
	 * type="java.lang.String"
	 * description="Scope to export variable to"
	 */
	public String getScope() {
		return scope;
	}

    /**
     * Sets scope of variable
     *
     * @param scope scope of variable to set
     * @see #scope
     */
	public void setScope(String scope) {
		this.scope = scope;
	}

    /**
     * Formats date and writes the result to the page or saves it to variable
     * of specified scope
     *
     * @throws JspException
     */
	public void doTag() throws JspException {

		PageContext pageContext = (PageContext) getJspContext();

		if ( value == null ) {
			value = new Date();
		}

		int dateStyle = DateFormat.MEDIUM;

		if ( "short".equalsIgnoreCase(this.dateStyle) ) {
			dateStyle = DateFormat.SHORT;
		} else if ( "long".equalsIgnoreCase(this.dateStyle) ) {
			dateStyle = DateFormat.LONG;
		} else if ( "full".equalsIgnoreCase(this.dateStyle) ) {
			dateStyle = DateFormat.FULL;
		}

		int timeStyle = DateFormat.MEDIUM;

		if ( "short".equalsIgnoreCase(this.timeStyle) ) {
			timeStyle = DateFormat.SHORT;
		} else if ( "long".equalsIgnoreCase(this.timeStyle) ) {
			timeStyle = DateFormat.LONG;
		} else if ( "full".equalsIgnoreCase(this.timeStyle) ) {
			timeStyle = DateFormat.FULL;
		}


		if ( !"date".equalsIgnoreCase(type) && !"time".equalsIgnoreCase(type) && !"datetime".equalsIgnoreCase(type) ) {
			type = "date";
		}

		Locale locale = null;
		TagUtils tagUtils = TagUtils.getInstance();
		if ( this.locale == null ) {
			locale = (Locale) pageContext.getAttribute(Globals.LOCALE_KEY, PageContext.SESSION_SCOPE);
		} else if ( this.locale instanceof Locale ) {
			locale = (Locale) this.locale;
		} else if ( this.locale instanceof String ) {
			String localeIdentifier = (String) this.locale;
			String[] localeElements = localeIdentifier.split("_");
			if ( localeElements.length >= 1 ) {
				String language = localeElements[0];
				if ( localeElements.length >= 2 ) {
					String country = localeElements[1];
					if ( localeElements.length >= 3 ) {
						String variant = localeElements[2];
						locale = new Locale(language, country, variant);
					} else {
						locale = new Locale(language, country);
					}
				} else {
					locale = new Locale(language);
				}
			}
		} else {
			JspTagException e = new JspTagException("Only instances of java.lang.String and java.util.Locale are supported in 'locale' attribute");
			tagUtils.saveException(pageContext, e);
			throw e;
		}

		String result = "";
		if ( value != null ) {
			Date date;
			if ( value instanceof String ) {
				String s = (String) value;
				if ( s.equalsIgnoreCase("now") ) {
					date = new Date();
				} else if ( s.equalsIgnoreCase("epoch") ) {
					date = new Date(0);
				} else {
                    try {
                        date = DateUtil.parse((String) value, new Locale("en"),
                                              DateUtil.getDatePattern());
                    } catch (ParseException e) {
                        throw new JspException(e);
                    }
				}
			} else if ( value instanceof Date ) {
				date = (Date) value;
			} else if ( value instanceof Calendar ) {
				date = ((Calendar) value).getTime();
			} else {
				JspTagException e = new JspTagException("Instances of class " + value.getClass().getName() + " are not supported by this tag");
				tagUtils.saveException(pageContext, e);
				throw e;
			}
            if (!GenericValidator.isBlankOrNull(pattern)) {
                result = DateUtil.format(date, locale, pattern);
            } else {
                if ( "date".equalsIgnoreCase(type) ) {
                    result = DateUtil.formatDate(date, locale, dateStyle);
                } else if ( "time".equalsIgnoreCase(type) ) {
                    result = DateUtil.formatTime(date, locale, timeStyle);
                } else /* datetime */ {
                    result = DateUtil.formatDateTime(date, locale, dateStyle, timeStyle);
                }
            }
		}

		if ( var != null ) {
			int varScope = PageContext.PAGE_SCOPE;
			if ( scope != null ) {
				varScope = tagUtils.getScope(scope);
			}
			pageContext.setAttribute(var, result, varScope);
		} else {
			tagUtils.write(pageContext, result);
		}
	}

}
